package com.shelpe.services.sinbad.service.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.shelpe.services.sinbad.controller.InvokesChunk;
import com.shelpe.services.sinbad.controller.KeywordsController.PricingKeyword;
import com.shelpe.services.sinbad.dao.KeywordMapper;
import com.shelpe.services.sinbad.entity.jpa.Adgroup;
import com.shelpe.services.sinbad.entity.jpa.Campaign;
import com.shelpe.services.sinbad.entity.jpa.KeywordsLog;
import com.shelpe.services.sinbad.entity.jpa.User;
import com.shelpe.services.sinbad.entity.jpa.vo.DisplayKeywordVo;
import com.shelpe.services.sinbad.entity.jpa.vo.KeywordsVo;
import com.shelpe.services.sinbad.model.Keyword;
import com.shelpe.services.sinbad.model.KeywordExample;
import com.shelpe.services.sinbad.repository.jpa.KeywordsLogRepository;
import com.shelpe.services.sinbad.repository.jpa.UserRepository;
import com.shelpe.services.sinbad.service.simbaproxy.SimbaProxyClient;
import com.shelpe.services.sinbad.service.simbaproxy.SimbaProxyClient.AddKeywordsResponse;
import com.shelpe.services.sinbad.service.simbaproxy.SimbaProxyClient.DeleteKeywordsResponse;
import com.shelpe.services.sinbad.service.simbaproxy.SimbaProxyClient.PriceKeywordsResponse;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import rx.Observable;
import rx.functions.Action1;

@Service
@Slf4j
public class OopKeywordService {
	
	@Autowired
	private ObjectMapper objectMapper;
	@Autowired
	private KeywordMapper keywordMapper;
	@Autowired
	private SimbaProxyClient simbaProxyClient;
	@Autowired
	private KeywordsLogRepository keywordsLogRepository;
	@Autowired
	private UserRepository userRepository;
	
	public void price(final User user, List<PricingKeyword> keywords, Callback lastCallback, final boolean isManual, final boolean isSemiauto){
		InvokesChunk<PricingKeyword> chunk = new InvokesChunk<PricingKeyword>();
		List<Long> keywordIDs = new ArrayList<Long>();
		for(PricingKeyword keyword: keywords){
			if(keyword.getKeywordID() > 0){
				chunk.add(keyword);
				keywordIDs.add(keyword.getKeywordID());	
			}
		}
		KeywordExample ex = new KeywordExample();
		ex.setUserId(user.getUserID());
		ex.createCriteria().andKeywordIdIn(keywordIDs);
		List<Keyword> originalKeywords = this.keywordMapper.selectByExample(ex);
		
		final AtomicInteger counter = new AtomicInteger();
		final List<Keyword> savedKeywords = new ArrayList<Keyword>();
		final KeywordsLogRepository logRepository = this.keywordsLogRepository;
		while(chunk != null){
			this.pricingPerChunk(chunk, new InvokesChunk.ChunkCallback<PricingKeyword, Keyword>(){
				@Override
				public void call(List<PricingKeyword> inputs, List<Keyword> results) {
					if(results != null){
						savedKeywords.addAll(results);
						//保存操作记录
						if(results.size() > 0){
							List<KeywordsLog> kwLogs = new ArrayList<KeywordsLog>();
							KeywordsLog.Action action = isManual?KeywordsLog.Action.MANUAL_CHANGE_PRICE:KeywordsLog.Action.AUTO_CHANGE_PRICE;
							if(isSemiauto){
								action = KeywordsLog.Action.SEMIAUTO_EXEC_UPDATE;
							}
							for(Keyword kw:originalKeywords){
								Keyword changedKeyword = filterByKeywordID(results, kw.getKeywordId());
								KeywordsLog kwLog = convert2Log(kw, changedKeyword!=null?changedKeyword:kw, action);
								kwLogs.add(kwLog);
							}
							if(kwLogs.size() > 0){
								logRepository.save(kwLogs);
							}
						}
					}
					
					//最后一次调用
					if(counter.decrementAndGet() == 0){
						log.debug("the last callback for priceKeywords", user);
						lastCallback.call(savedKeywords);
					}
				}
			}, user);
			chunk = chunk.getNext();
			counter.incrementAndGet();
		}
	}
	
	public void add(Adgroup adgroup, final List<PricingKeyword> keywords, final Callback lastCallback, 
			final boolean isManual){
		boolean check = false;
		for(PricingKeyword pkw:keywords){
			if(pkw.getMaxMobilePrice() > 0){
				check = true;
				break;
			}
		}
		final boolean needSetMobilePrice = check;
		final User user = this.userRepository.findOne(adgroup.getUserID());
		Assert.notNull(user, "user_not_found");
		
		this.addInternal(adgroup, keywords, new Callback(){
			@Override
			public void call(List<Keyword> savedKeywords) {
				//如果需要设置移动价格，需要做一次调价操作
				if(needSetMobilePrice && savedKeywords != null && savedKeywords.size() > 0){
					List<PricingKeyword> toPriceKeywords = new ArrayList<PricingKeyword>();
					for(PricingKeyword pkw:keywords){
						Keyword addedKeyword = filterByWord(savedKeywords, pkw.getWord());
						if(addedKeyword !=null){
							pkw.setKeywordID(addedKeyword.getKeywordId());
							pkw.setMobileIsDefaultPrice(0);
							if(pkw.getMatchScope() == -1){
								pkw.setMatchScope(pkw.getMatchScope());
							}
							toPriceKeywords.add(pkw);
						}
					}
					if(toPriceKeywords.size() > 0){
						price(user, keywords, lastCallback, isManual, false);
					}else{
						log.warn("no mobile price to set: {}", adgroup);
					}
				}
				lastCallback.call(savedKeywords);
			}
		}, isManual);
	}
	private void addInternal(final Adgroup adgroup, List<PricingKeyword> keywords, Callback lastCallback, boolean isManual){
		InvokesChunk<PricingKeyword> chunk = new InvokesChunk<PricingKeyword>();
		for(PricingKeyword keyword: keywords){
			chunk.add(keyword);
		}
		
		final AtomicInteger counter = new AtomicInteger();
		final List<Keyword> savedKeywords = new ArrayList<Keyword>();
		final KeywordsLogRepository logRepository = this.keywordsLogRepository;
		while(chunk != null){
			this.addingPerChunk(chunk, new InvokesChunk.ChunkCallback<PricingKeyword, Keyword>(){
				@Override
				public void call(List<PricingKeyword> inputs, List<Keyword> results) {
					if(results != null){
						savedKeywords.addAll(results);
						
						//保存操作记录
						if(results.size() > 0){
							List<KeywordsLog> kwLogs = new ArrayList<KeywordsLog>();
							KeywordsLog.Action action = isManual?KeywordsLog.Action.MANUAL_NEW_KEYWORD:KeywordsLog.Action.AUTO_NEW_KEYWORD;
							for(Keyword kw:results){
								KeywordsLog kwLog = convert2Log(null, kw, action);
								kwLogs.add(kwLog);
							}
							if(kwLogs.size() > 0){
								logRepository.save(kwLogs);
							}
						}
					}
					
					//最后一次调用
					if(counter.decrementAndGet() == 0){
						log.debug("the last callback for addKeywords", adgroup);
						lastCallback.call(savedKeywords);
					}
				}
			}, adgroup);
			chunk = chunk.getNext();
			counter.incrementAndGet();
		}
	}
	
	public void delete(final Campaign campaign, List<Long> keywordIDs, Callback lastCallback, final boolean isManual, final boolean isSemiauto){
		InvokesChunk<DeletingKeyword> chunk = new InvokesChunk<DeletingKeyword>();
		for(long id: keywordIDs){
			DeletingKeyword keyword = new DeletingKeyword();
			keyword.setKeywordID(id);
			chunk.add(keyword);
		}
		
		KeywordExample ex = new KeywordExample();
		ex.setUserId(campaign.getUserID());
		ex.createCriteria().andKeywordIdIn(keywordIDs);
		List<Keyword> originalKeywords = this.keywordMapper.selectByExample(ex);
		
		final AtomicInteger counter = new AtomicInteger();
		final List<Keyword> savedKeywords = new ArrayList<Keyword>();
		final KeywordsLogRepository logRepository = this.keywordsLogRepository;
		while(chunk != null){
			this.deletePerChunk(chunk, new InvokesChunk.ChunkCallback<DeletingKeyword, Keyword>(){
				@Override
				public void call(List<DeletingKeyword> inputs, List<Keyword> results) {
					if(results != null){
						savedKeywords.addAll(results);
						
						//保存操作记录
						if(results.size() > 0){
							List<KeywordsLog> kwLogs = new ArrayList<KeywordsLog>();
							KeywordsLog.Action action = isManual?KeywordsLog.Action.MANUAL_DEL_KEYWORD:KeywordsLog.Action.AUTO_DEL_KEYWORD;
							if(isSemiauto){
								action = KeywordsLog.Action.SEMIAUTO_EXEC_DEL;
							}
							for(Keyword kw:originalKeywords){
								KeywordsLog kwLog = convert2Log(kw, null, action);
								kwLogs.add(kwLog);
							}
							if(kwLogs.size() > 0){
								logRepository.save(kwLogs);
							}
						}
					}
					
					//最后一次调用
					if(counter.decrementAndGet() == 0){
						log.debug("the last callback for deleteKeywords", campaign);
						lastCallback.call(savedKeywords);
					}
				}
			}, campaign);
			chunk = chunk.getNext();
			counter.incrementAndGet();
		}
	}
	
	public static interface Callback{
		void call(List<Keyword> keywords);
	}
	
	private Keyword filterByKeywordID(List<Keyword> keywords, long keywordID){
		for(Keyword kw: keywords){
			if(kw.getKeywordId() == keywordID){
				return kw;
			}
		}
		return null;
	}
	private Keyword filterByWord(List<Keyword> keywords, String word){
		for(Keyword kw: keywords){
			if(word.equals(kw.getWord())){
				return kw;
			}
		}
		return null;
	}
	
	private void deletePerChunk(final InvokesChunk<DeletingKeyword> chunk, 
			final InvokesChunk.ChunkCallback<DeletingKeyword, Keyword> next,
			final Campaign campaign){
		if(chunk.getWords() == null || chunk.getWords().size() == 0){
			next.call(chunk.getWords(), null);
			return;
		}
		
		List<Long> keywordIDs = new ArrayList<Long>();
		for(DeletingKeyword keyword: chunk.getWords()){
			keywordIDs.add(keyword.getKeywordID());
		}
		
		Observable<SimbaProxyClient.DeleteKeywordsResponse> observer = this.simbaProxyClient.removeKeywords(campaign.getNick(), campaign.getAutoID(), keywordIDs);
		observer.subscribe(new Action1<SimbaProxyClient.DeleteKeywordsResponse>(){
			@Override
			public void call(DeleteKeywordsResponse res) {
				log.debug("deleteKeywords: {}", res);
				List<Keyword> keywords = new ArrayList<Keyword>();
				if(res != null && res.getRes() != null && res.getRes().getDataList() != null && res.getRes().getDataList().getKeywords() != null){
					List<Long> deletedKeywordIDs = new ArrayList<Long>();
					for(SimbaProxyClient.Keyword kw: res.getRes().getDataList().getKeywords()){
						deletedKeywordIDs.add(kw.getKeywordId());
						Keyword keyword = kw.toPO(new Keyword());
						keyword.setUserId(campaign.getUserID());
						keyword.setNick(campaign.getNick());
						keyword.setUpdatedTime(new Date());
						keywords.add(keyword);
					}
					if(deletedKeywordIDs.size() > 0){
						KeywordExample ex = new KeywordExample();
						ex.setUserId(campaign.getUserID());
						ex.createCriteria().andKeywordIdIn(keywordIDs);
						keywordMapper.deleteByExample(ex);
					}
				}else{
					log.error("fail to sync taobao when deleteKeywords: {} - {}", res!=null?res.getError():"", keywordIDs);
				}
				next.call(chunk.getWords(), keywords);
			}
		}, new Action1<Throwable>(){
			@Override
			public void call(Throwable t) {
				log.error("catch exception when deleteKeywords: {} - {}", keywordIDs, t);
				next.call(chunk.getWords(), null);
			}
		});
	}
	
	private void pricingPerChunk(final InvokesChunk<PricingKeyword> chunk, 
			final InvokesChunk.ChunkCallback<PricingKeyword, Keyword> next,
			final User user){
		if(chunk.getWords() == null || chunk.getWords().size() == 0){
			next.call(chunk.getWords(), null);
			return;
		}
		
		List<SimbaProxyClient.HandlingKeyword> kws = new ArrayList<SimbaProxyClient.HandlingKeyword>();
		for(PricingKeyword keyword: chunk.getWords()){
			SimbaProxyClient.HandlingKeyword kw = new SimbaProxyClient.HandlingKeyword();
			BeanUtils.copyProperties(keyword, kw);
			kws.add(kw);
		}
		String kwString = this.serializeHandlingKeyword(kws);
		log.debug("pricing words: {}", kwString);
		Assert.notNull(kwString, "fail to serialize the keywords");
		
		Observable<SimbaProxyClient.PriceKeywordsResponse> observer = this.simbaProxyClient.updateKeywords(user.getNick(), kwString);
		observer.subscribe(new Action1<SimbaProxyClient.PriceKeywordsResponse>(){
			@Override
			public void call(PriceKeywordsResponse res) {
				log.debug("priceKeywords: {}", res);
				List<Keyword> keywords = new ArrayList<Keyword>();
				if(res != null && res.getRes() != null && res.getRes().getDataList() != null && res.getRes().getDataList().getKeywords() != null){
					for(SimbaProxyClient.Keyword kw: res.getRes().getDataList().getKeywords()){
						Keyword keyword = kw.toPO(new Keyword());
						keyword.setMatchscope(kw.getMatchscope());
						keyword.setMaxPrice(kw.getMaxPrice());
						keyword.setIsDefaultPrice(kw.isDefaultPrice());
						keyword.setMaxMobilePrice(kw.getMaxMobilePrice());
						keyword.setMobileIsDefaultPrice(kw.getMobileIsDefaultPrice());
						keyword.setUserId(user.getUserID());
						keyword.setKeywordId(kw.getKeywordId());
						keyword.setQscore(null);
						keywordMapper.updateByPrimaryKeySelective(keyword);
						keywords.add(keyword);
					}
				}else{
					log.error("fail to sync taobao when priceKeywords: {} - {}", res!=null?res.getError():"", kws);
				}
				next.call(chunk.getWords(), keywords);
			}
		}, new Action1<Throwable>(){
			@Override
			public void call(Throwable t) {
				log.error("catch exception when priceKeywords: {} - {}", kws, t);
				next.call(chunk.getWords(), null);
			}
		});
	}

	private void addingPerChunk(final InvokesChunk<PricingKeyword> chunk, 
			final InvokesChunk.ChunkCallback<PricingKeyword, Keyword> callback,
			final Adgroup adgroup){
		if(chunk.getWords() == null || chunk.getWords().size() == 0){
			callback.call(chunk.getWords(), null);
			return;
		}
		
		List<SimbaProxyClient.AddingKeyword> kws = new ArrayList<SimbaProxyClient.AddingKeyword>();
		for(PricingKeyword keyword: chunk.getWords()){
			SimbaProxyClient.AddingKeyword kw = new SimbaProxyClient.AddingKeyword();
			BeanUtils.copyProperties(keyword, kw);
			kws.add(kw);
		}
		String kwString = this.serializeAddingKeyword(kws);
		log.debug("add words: {}", kwString);
		Assert.notNull(kwString, "fail to serialize the keywords");
		
		Observable<SimbaProxyClient.AddKeywordsResponse> observer = this.simbaProxyClient.createKeywords(adgroup.getNick(), adgroup.getAutoID(), kwString);
		observer.subscribe(new Action1<SimbaProxyClient.AddKeywordsResponse>(){
			@Override
			public void call(AddKeywordsResponse res) {
				log.debug("addKeywords: {}", res);
				List<Keyword> keywords = new ArrayList<Keyword>();
				if(res != null && res.getRes() != null && res.getRes().getDataList() != null && res.getRes().getDataList().getKeywords() != null){
					for(SimbaProxyClient.Keyword kw: res.getRes().getDataList().getKeywords()){
						Keyword keyword = kw.toPO(new Keyword());
						keyword.setMobileIsDefaultPrice(1);
						if(adgroup.getMobileDiscount() > 0){
							keyword.setMaxMobilePrice(keyword.getMaxPrice() * adgroup.getMobileDiscount() / 100);	
						}
						keyword.setUserId(adgroup.getUserID());
						keyword.setNick(adgroup.getNick());
						keyword.setUpdatedTime(new Date());
						keyword.setOrder((short)0);
						keyword.setQscore(0);
						keyword.setSmartPrice(false);
						keyword.setWirelessQscore(0);
						keyword.setRankScore((short)0);
						keywordMapper.insertSelective(keyword);
						keywords.add(keyword);
					}
				}else{
					log.error("fail to sync taobao when addKeywords: {} - adgroup[{}], {}", res!=null?res.getError():"", adgroup.getAutoID(), kws);
				}
				callback.call(chunk.getWords(), keywords);
			}
		}, new Action1<Throwable>(){
			@Override
			public void call(Throwable t) {
				log.error("catch exception when addKeywords: {} - {}", kws, t);
				callback.call(chunk.getWords(), null);
			}
		});
	}
	
	
	private String serializeAddingKeyword(List<SimbaProxyClient.AddingKeyword> keywords){
		try {
			return this.objectMapper.writer().writeValueAsString(keywords);
		} catch (JsonProcessingException e) {
			log.error("JsonProcessingException", e);
		}
		return null;
	}
	
	private String serializeHandlingKeyword(List<SimbaProxyClient.HandlingKeyword> keywords){
		try {
			return this.objectMapper.writer().writeValueAsString(keywords);
		} catch (JsonProcessingException e) {
			log.error("JsonProcessingException", e);
		}
		return null;
	}
	
	private KeywordsLog convert2Log(Keyword original, Keyword latest, KeywordsLog.Action action){
		KeywordsLog kwLog = new KeywordsLog();
		kwLog.setUpdatedTime(new Date());
		kwLog.setAction(action);
		
		DisplayKeywordVo note = new DisplayKeywordVo();
		if(latest != null){
			kwLog.setAdgroupID(latest.getAdgroupId());
			kwLog.setKeywordID(latest.getKeywordId());
			kwLog.setKeyword(latest.getWord());
			KeywordsVo.Vo newNote = new KeywordsVo.Vo();
			BeanUtils.copyProperties(latest, newNote);
			note.setNewNote(newNote);
		}
		if(original != null){
			kwLog.setAdgroupID(original.getAdgroupId());
			kwLog.setKeywordID(original.getKeywordId());
			kwLog.setKeyword(original.getWord());
			KeywordsVo.Vo oldNote = new KeywordsVo.Vo();
			BeanUtils.copyProperties(original, oldNote);
			note.setOldNote(oldNote);
		}
		
		String jsonStr = "";
		try {
			jsonStr = objectMapper.writer().writeValueAsString(note);
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}
		kwLog.setNote(jsonStr);
		return kwLog;
	}
	
	@Data
	public static class DeletingKeyword implements InvokesChunk.Lengthable{
		private long keywordID;
		@Override
		public int length() {
			return 50;
		}
	}
}
